<template>
	<view class="e-tabs">
		<view class="e-tabs__wrapper">
			<slot name="left" />
			<view class="e-tabs__wrapper__scroll-view-wrapper">
				<scroll-view :scroll-x="props.scrollable" :scroll-left="scrollLeft" scroll-with-animation
					class="e-tabs__wrapper__scroll-view" :id="`e-tabs__wrapper__scroll-view-${elId}`"
					:ref="`e-tabs__wrapper__scroll-view-${elId}`" :show-scrollbar="false">
					<view class="e-tabs__wrapper__nav">
						<view class="e-tabs__wrapper__nav__item" v-for="(item, index) in propsList" :key="index"
							@tap="clickHandler(item, index)" :id="`e-tabs__wrapper__nav__item-${elId}-${index}`"
							:ref="`e-tabs__wrapper__nav__item-${elId}-${index}`"
							:style="[addStyle(props.itemStyle),addStyle(index === innerCurrent ? props.itemActiveStyle :''),{flex: props.scrollable ? '' : 1}]"
							:class="[is('disabled',item.disabled)]">
							<text :class="[is('disabled',item.disabled)]"
								class="e-tabs__wrapper__nav__item__text"
								:style="[textStyle(index)]">{{ item[props.keyName] }}</text>
							<e-badge :show="!!(item.badge && (item.badge.show || item.badge.isDot || item.badge.value))"
								:isDot="item.badge && item.badge.isDot || badgeProps.isDot"
								:value="item.badge && item.badge.value || badgeProps.value"
								:max="item.badge && item.badge.max || badgeProps.max"
								:type="item.badge && item.badge.type || badgeProps.type"
								:showZero="item.badge && item.badge.showZero || badgeProps.showZero"
								:bgColor="item.badge && item.badge.bgColor || badgeProps.bgColor"
								:color="item.badge && item.badge.color || badgeProps.color"
								:shape="item.badge && item.badge.shape || badgeProps.shape"
								:numberType="item.badge && item.badge.numberType || badgeProps.numberType"
								:inverted="item.badge && item.badge.inverted || badgeProps.inverted"
								customStyle="margin-left: 4px;"></e-badge>
						</view>
						<view class="e-tabs__wrapper__nav__line" ref="navLineRef" :style="[lineStyle]">
							<slot name="block" />
						</view>

					</view>
				</scroll-view>
			</view>
			<slot name="right" />
		</view>
	</view>
</template>

<script lang="ts">
	import {defineComponent} from 'vue'
	import mpMixin from '../../libs/mixin/mpMixin'
	/**
	 * Tabs 标签
	 * @description tabs标签组件，在标签多的时候，可以配置为左右滑动，标签少的时候，可以禁止滑动。 该组件的一个特点是配置为滚动模式时，激活的tab会自动移动到组件的中间位置。
	 * @tutorial https://www.uviewui.com/components/tabs.html
	 * @property {String | Number}	duration			滑块移动一次所需的时间，单位秒（默认 200 ）
	 * @property {String | Number}	swierWidth			swiper的宽度（默认 '750rpx' ）
	 * @property {String}	keyName	 从`list`元素对象中读取的键名（默认 'name' ）
	 * @event {Function(index)} change 标签改变时触发 index: 点击了第几个tab，索引从0开始
	 * @event {Function(index)} click 点击标签时触发 index: 点击了第几个tab，索引从0开始
	 * @event {Function(index)} longPress 长按标签时触发 index: 点击了第几个tab，索引从0开始
	 * @example <e-tabs :list="list" :is-scroll="false" :current="current" @change="change" @longPress="longPress"></e-tabs>
	 */
	export default defineComponent({
		name: "e-tabs",
		mixins: [mpMixin]
	})
</script>

<script lang="ts" setup>
	import {
		ref,
		computed,
		watch,
		nextTick,
		onMounted,
		toRefs,
		getCurrentInstance
	} from 'vue'
	import {
		addUnit,
		addStyle,
		getPx,
	} from '../../libs/utils/_utils'
	import {
		deepMerge,
		arrayDeepClone,
		sleep,
		sys,
		getRect,
		getNvueRect,
		nanoid,
		type rectSize
	} from '../../libs/utils'
	import type {
		StyleProperties
	} from "../../libs/tokens";
	import {
		tabsProps,
		tabsEmits
	} from './props';
	import {
		useNamespace,
		useDefaultProps
	} from '../../libs/hooks'
	import componentsProps, {
		type DefaultProps
	} from "../../libs/config/componentsProps";

	

	const {
		tabs: tabsDefaultProps
	} = toRefs(componentsProps);
	const {
		badge: badgeProps
	} = toRefs(componentsProps);

	const _props = defineProps(tabsProps)

	const props = useDefaultProps < DefaultProps['tabs'] > (_props, tabsDefaultProps);
	const emit = defineEmits(tabsEmits)

	const elId = ref(nanoid(21, true))
	const propsList = ref < Required<DefaultProps['tabs']>['list'] > ([])

	const firstTime = ref(false)
	const scrollLeft = ref(0)
	const lineOffsetLeft = ref(0)
	let tabsRect = {
		left: 0,
		right: 0,
		width: 0
	}
	let scrollViewWidth = 0
	const innerCurrent = ref(0)
	
	const ctx = getCurrentInstance()
	
	const {
		is
	} = useNamespace('tabs')

	const isBlockSlot = computed(() => {
		
		return ctx?.slots?.block ? true : false
	})

	const lineStyle = computed(() => {
		return {
			width: addUnit(props.value.lineWidth),
			height: addUnit(props.value.lineHeight),
			background: isBlockSlot.value ? 'transparent' : props.value.lineColor,
			// #ifndef APP-NVUE
			transform: `translate(${lineOffsetLeft.value}px)`,
			transitionDuration: `${firstTime.value ? 0 : props.value.duration}ms`
			// #endif

		}
	})
	

	const textStyle = (index: number) => {
		const style: StyleProperties = {}
		// 取当期是否激活的样式
		const customeStyle = index === innerCurrent.value ? addStyle(props.value.activeStyle as Record < string, any > ) :
			addStyle(
				props.value.inactiveStyle as Record < string, any > )
		// 如果当前菜单被禁用，则通过去除style的color来保证disabled样式生效
		if (props.value?.list?. [index]?.disabled) {
			style.color = undefined
		}
		return deepMerge(customeStyle as Record < string, any > , style)

	}


	const setLineLeft = () => {
		const tabItem = propsList.value[innerCurrent.value];
		if (!tabItem) {
			return;
		}
		// 获取滑块该移动的位置
		let _lineOffsetLeft = propsList.value
			.slice(0, innerCurrent.value)
			.reduce((total, curr) => total + curr.rect.width, 0);
		// 获取下划线的数值px表示法
		const lineWidth = Number(getPx(props.value.lineWidth as number));
		lineOffsetLeft.value = _lineOffsetLeft + (tabItem.rect.width - lineWidth) / 2
		// #ifdef APP-NVUE
		// 第一次移动滑块，无需过渡时间
		_animation(lineOffsetLeft.value, firstTime.value ? 0 : parseInt(`${props.value.duration}`))
		// #endif

		// 如果是第一次执行此方法，让滑块在初始化时，瞬间滑动到第一个tab item的中间
		// 这里需要一个定时器，因为在非nvue下，是直接通过style绑定过渡时间，需要等其过渡完成后，再设置为false(非第一次移动滑块)
		if (firstTime.value) {
			setTimeout(() => {
				firstTime.value = false
			}, 10);
		}
	}
	const navLineRef = ref(null)
	// nvue下设置滑块的位置
	let animation:any
	const _animation = (x: number, duration = 0) => {
		// #ifdef APP-NVUE
		animation.transition(navLineRef.value, {
			styles: {
				transform: `translateX(${x}px)`
			},
			duration
		})
		// #endif
	}
	// 点击某一个标签
	const clickHandler = (item: any, index: number | string) => {
		// 因为标签可能为disabled状态，所以click是一定会发出的，但是change事件是需要可用的状态才发出
		emit('click', {
			data: item,
			index
		})
		// 如果disabled状态，返回
		if (item.disabled) return
		innerCurrent.value = Number(index)
		resize()
		emit('change', {
			data: item,
			index
		})
	}
	const setScrollLeft = () => {
		// 当前活动tab的布局信息，有tab菜单的width和left(为元素左边界到父元素左边界的距离)等信息
		const tabRect = propsList.value[innerCurrent.value]
		// 累加得到当前item到左边的距离
		const offsetLeft = propsList.value
			.slice(0, innerCurrent.value)
			.reduce((total, curr) => {
				return total + curr.rect.width
			}, 0)
		// 此处为屏幕宽度
		const windowWidth = sys().windowWidth
		// 将活动的tabs-item移动到屏幕正中间，实际上是对scroll-view的移动
		let _scrollLeft = offsetLeft - (tabsRect.width - tabRect.rect.width) / 2 - (windowWidth - tabsRect
			.right) / 2 + tabsRect.left / 2
		// 这里做一个限制，限制scrollLeft的最大值为整个scroll-view宽度减去tabs组件的宽度
		_scrollLeft = Math.min(_scrollLeft, scrollViewWidth - tabsRect.width)
		scrollLeft.value = Math.max(0, _scrollLeft)
	}
	// 获取所有标签的尺寸
	const resize = () => {
		
		// 如果不存在list，则不处理
		if (!props.value.list || props.value.list.length === 0) {
			return
		}
		
		// firstTime.value = true
		Promise.all([queryRect(`e-tabs__wrapper__scroll-view-${elId.value}`), getAllItemRect()]).then(([tabsRect,
			itemRect = []
		]) => {
			tabsRect = tabsRect
			scrollViewWidth = 0
			itemRect.forEach((item, index) => {
				// 计算scroll-view的宽度，这里
				scrollViewWidth += item.width!
				// 另外计算每一个item的中心点X轴坐标
				propsList.value[index].rect = item

			})
			if(scrollViewWidth === 0){
				sleep(20).then(() => {
					resize()
				})
				return 
			}
			// 获取了tabs的尺寸之后，设置滑块的位置
			setLineLeft()
			setScrollLeft()
		})
	}
	// 获取所有标签的尺寸
	const getAllItemRect = (): Promise < rectSize[] > => {
		return new Promise(resolve => {
			const promiseAllArr = props.value.list.map((item, index) => queryRect(
				`e-tabs__wrapper__nav__item-${elId.value}-${index}`, true))
			Promise.all(promiseAllArr).then(sizes => resolve(sizes))
		})
	}
	// 获取各个标签的尺寸
	const queryRect = (el: string, item ? : boolean): Promise < rectSize > => {
		// #ifndef APP-NVUE
		return new Promise(resolve => {
			getRect(ctx,`#${el}`).then(size => {
				resolve(size)
			})
		})
		// #endif

		// #ifdef APP-NVUE
		return new Promise(resolve => {
			getNvueRect(item ? (ctx?.proxy?.$refs?.[el] as any[])[0] : ctx?.proxy?.$refs[el]).then(size => {
				resolve(size)
			})
		})
		// #endif
	}

	



	watch(() => props.value.current, (newValue) => {
		// 内外部值不相等时，才尝试移动滑块
		if (newValue !== innerCurrent.value) {
			innerCurrent.value = newValue as number
			nextTick(() => {
				resize()
			})
		}

	}, {
		immediate: true,
	})

	watch(() => props.value.list, () => {
		propsList.value = arrayDeepClone(props.value.list)
		// list变化时，重新渲染list各项信息
		nextTick(() => {
			resize()
		})
	})
	
	onMounted(async () => {
		// #ifdef APP-NVUE
		animation = uni.requireNativePlugin('animation')
		
		// #endif
		propsList.value = arrayDeepClone(props.value.list)
		await sleep(20)
		resize()
		
	})
</script>

<style lang="scss" scoped>
	@import "../../libs/theme/tabs.scss";
</style>
